package com.ke.rule.base;

import com.google.common.base.Splitter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * 服务方法的调用时延和分解结构，对执行过程进行跟踪，以便输出报告辅助诊断
 * 
 * @author guyu
 */
public class CallLatency {
  private static final Logger logger = LoggerFactory.getLogger(CallLatency.class);
  
  static class Breakdown {
    String name;        // 项目名称
    String extension;   // 扩展名, 每个Breakdown的name.extension需要保持唯一
    String group;   // 项目组
    long begin;     // 开始时间
    long end;       // 结束时间
    long repeatCount;   // 重复次数
    
    Breakdown(String name, String extension, String group) {
      super();
      Objects.requireNonNull(name, "name");
      Objects.requireNonNull(group, "group");
      
      this.name = name;
      this.extension = extension;
      this.group = group;
      this.begin = this.end = this.repeatCount = 0L;
    }

    static String id(String name, String extension) {
      return String.format("%s.%s", name, extension);
    }
    
    String id() {
      return id(name, extension);
    }
    
    Breakdown begin() {
      begin = System.currentTimeMillis();
      end = 0L;
      repeat();
      return this;
    }
    
    void repeat() {
      repeatCount++;
    }
    
    boolean isRunning() {
      return begin > 0 && end <= 0;
    }
    
    boolean isEnd() {
      return end > 0;
    }
    
    void end() {
      if (!isEnd()) {
        this.end = System.currentTimeMillis();
      }
    }
    
    long elapsed() {
      return end - begin;
    }
    
    boolean isUltraFast() {
      return elapsed() < 1;
    }

    @Override
    public String toString() {
      return "Breakdown [name=" + name + ", extension=" + extension + ", group=" + group
          + ", begin=" + begin + ", end=" + end + ", repeatCount=" + repeatCount + "]";
    }
  }

  public static class Breakdowns extends Breakdown {
    List<Breakdown> list;
    Map<String, Breakdown> map;
    
    Breakdowns() {
      super("", null, "total");
      list = new ArrayList<>();
      map = new HashMap<>();
    }
    
    @Override
    Breakdown begin() {
      super.begin();
      map.clear();
      list.clear();
      return this;
    }
    
    @Override
    void end() {
      super.end();
      // 遍历列表全部结束
      list.forEach(breakdown -> breakdown.end());
    }
    
    void begin(String name, String extension, String group) {
      if (!isRunning()) {
        logger.warn(String.format("Breakdown(%s, %s, %s) cannot be added while it‘s not running",
            name, extension, group));
        return;
      }
      String breakdownId = Breakdown.id(name, extension);
      Breakdown breakdown = map.get(breakdownId);
      if (null == breakdown) {
        breakdown = new Breakdown(name, extension, group);
        list.add(breakdown.begin());
        map.put(breakdownId, breakdown);
      } else {
        breakdown.repeat();
        logger.debug("Breakdown '{}' already exists, This is a repeat call, count: {}", breakdownId, breakdown.repeatCount);
      }
    }
    
    void end(String name, String extension) {
      if (!isRunning()) {
        logger.warn(String.format("Breakdown (%s, %s) cannot be ended while it‘s not running", name, extension));
        return;
      }
      String breakdownId = Breakdown.id(name, extension);
      Breakdown breakdown = map.get(breakdownId);
      if (null == breakdown) {
        logger.debug("'" + breakdownId + "' not found at CallLatency.end");
      } else {
        breakdown.end();
      }
    }
    
    @Override
    public long elapsed() {
      // 尾的结束 - 首的开始
      if (list.isEmpty()) {
        return -1;
      }
      Breakdown tail = list.get(list.size() - 1);
      if (!tail.isEnd()) {
        return -1;
      }
      Breakdown head = list.get(0);
      return tail.end - head.begin;
    }

    @Override
    public boolean isUltraFast() {
      long durt = elapsed();
      return durt >= 0 && durt <= 800;
    }

    Breakdown max(String group) {
      Breakdown slowest = list.stream().filter(bd -> bd.group.equals(group))
          .max((lhs, rhs) -> Long.compare(lhs.elapsed(), rhs.elapsed())).orElse(null);
      
      return slowest;
    }

    /**
     * 将字符串左右填充空格，总长度限定在width
     * @param str
     * @param width
     * @param align
     * @return
     */
    private String padSpace(String str, int width, Header.Alignment align) {
      String paddedStr = "";
      switch (align) {
      case Left:
        paddedStr = StringUtils.rightPad(str, width);
        break;
      case Right:
        paddedStr = StringUtils.leftPad(str, width);
        break;
      case Center:
      default:
        int strLen = str.length();
        int padSize = width - strLen;
        int padSizeLeft = padSize / 2;
        
        paddedStr = StringUtils.leftPad(str, padSizeLeft + strLen);
        paddedStr = StringUtils.rightPad(paddedStr, width);
        break;
      }
      return paddedStr;
    }
    
    /**
     * 以表格形式输出字符串
     * @param group 输出该组中耗时最长的方法, null或组名不存在，则输出倒数第二组
     * @return
     */
    public String prettyFormat(String group) {
      return  prettyFormat(group, true);
    }
    
    /**
     * 以表格形式输出字符串
     * @param group 输出该组中耗时最长的方法, null或组名不存在，则输出倒数第二组
     * @param skipZero 跳过0耗时的行
     * @return
     */
    public String prettyFormat(String group, boolean skipZero) {
      final String COL_NO = "No";
      final String COL_ELAPSED = "Elapsed";
      
      // 遍历列表，提取出group列表，以及每个group中的最大字符数
      Header header = new Header();
      
      header.addColumn(COL_NO, 3, Header.Alignment.Right);
      list.forEach(bd -> {
        header.transpose(bd.group, bd.name);
      });
      // only No. column
      if (header.columns.size() <= 1) {
        return "empty structure ...";
      }
      
      list.forEach(bd -> {
        header.transpose(COL_ELAPSED, String.valueOf(bd.elapsed()), Header.Alignment.Right);
      });

      // 表头
      StringBuilder headerBuilder = new StringBuilder();
      header.columns.forEach(col -> {
        if (headerBuilder.length() > 0) {
          headerBuilder.append("|");
        }
        headerBuilder.append(" ");
        headerBuilder.append(padSpace(col.title, Math.min(col.width, Header.MAX_COL_WIDTH), Header.Alignment.Center));
        headerBuilder.append(" ");
      });
      // 分割线
      StringBuilder breakBuilder = new StringBuilder();
      header.columns.forEach(col -> {
        if (breakBuilder.length() > 0) {
          breakBuilder.append("+");
        }
        breakBuilder.append(StringUtils.repeat('-', Math.min(col.width, Header.MAX_COL_WIDTH) + 2));
      });
      
      // 表体
      StringBuilder bodyBuilder = new StringBuilder();
      for (int lineNo = 0; lineNo < list.size(); lineNo++) {
        Breakdown bd = list.get(lineNo);

        // 跳过0耗时的行
        if (skipZero && bd.isUltraFast()) {
          continue;
        }

        String brkdwnName = StringUtils.strip(bd.name, " \t\r\n");
        boolean lineWrap = brkdwnName.length() > Header.MAX_COL_WIDTH;
        List<String> multiLineText = null;
        
        StringBuilder lineBuilder = new StringBuilder();
        for (Header.Column col : header.columns) {
          if (lineBuilder.length() > 0) {
            lineBuilder.append("|");
          }
          
          String content = "";
          if (col.title.equals(COL_NO)) {
            content = String.valueOf(1 + lineNo);
          } else if (col.title.equals(COL_ELAPSED)) {
            content = bd.repeatCount > 1 ? String.format("%d %d", bd.repeatCount, bd.elapsed()) : String.valueOf(bd.elapsed());
          } else if (col.title.equals(bd.group)) {
            if (lineWrap) {
              String wrappedText = WordUtils.wrap(brkdwnName, Header.MAX_COL_WIDTH, null, true, "\\.");
              multiLineText  = Splitter.on(System.lineSeparator()).omitEmptyStrings().trimResults().splitToList(wrappedText);
              content = multiLineText.get(0);
            } else {
              content = brkdwnName;
            }
          }
          
          lineBuilder.append(" ");
          lineBuilder.append(padSpace(content, Math.min(col.width, Header.MAX_COL_WIDTH), col.align));
          lineBuilder.append(" ");
        }
        bodyBuilder.append(lineBuilder.toString()).append(System.lineSeparator());
        
        // 输出折行后的内容
        if (lineWrap) {
          for (int i = 1; i < multiLineText.size(); i++) {
            StringBuilder contLineBuilder = new StringBuilder();
            for (Header.Column col : header.columns) {
              if (contLineBuilder.length() > 0) {
                contLineBuilder.append(" ");
              }
              if (col.title.equals(bd.group)) {
                contLineBuilder.append(" ").append(multiLineText.get(i));
                break;
              } else {
                contLineBuilder.append(padSpace(" ", col.width + 2, col.align));
              }
            }
            bodyBuilder.append(contLineBuilder.toString()).append(System.lineSeparator());
          }
        }
      }
      
      StringBuilder footerBuilder = new StringBuilder();
      footerBuilder.append(String.format("(Total: %d", this.elapsed()));
      
      // 指定列最大耗时
      Breakdown maxBreakdown = null;
      if (StringUtils.isNotBlank(group)) {
        maxBreakdown = max(group);
      }
      if (null == maxBreakdown) {
        maxBreakdown = max(header.columns.get(header.columns.size() - 2).title);
      }
      if (null != maxBreakdown) {
        footerBuilder.append(String.format(", %s Max: %d %s", maxBreakdown.group, maxBreakdown.elapsed(), maxBreakdown.name));
      }
      
      footerBuilder.append(")");
      
      StringBuilder blockBuilder = new StringBuilder();
      blockBuilder.append(headerBuilder.toString()).append(System.lineSeparator());
      blockBuilder.append(breakBuilder.toString()).append(System.lineSeparator());
      blockBuilder.append(bodyBuilder.toString());
      blockBuilder.append(footerBuilder.toString());
      
      return blockBuilder.toString();
    }
    
    public String prettyFormat() {
      return prettyFormat(null);
    }
  }
  
  private static class Header {
    private static final int MAX_COL_WIDTH = 64;
    
    private enum Alignment {
      Left, Center, Right;
    }
    
    private static class Column {
      String title;
      int width;
      Alignment align;
      public Column(String title, int width, Alignment align) {
        super();
        this.title = title;
        this.width = width;
        this.align = align;
      }
    }
    
    private List<Column> columns = new ArrayList<Column>();
    
    /**
     * 新增列
     * @param title
     * @param width
     * @param align
     */
    void addColumn(String title, int width, Alignment align) {
      columns.add(new Column(title, width, align));
    }
    
    /**
     * 根据组名获取列对象
     * @param group
     * @return
     */
    Column findColumn(String group) {
      return columns.stream().filter(c -> c.title.equals(group)).findAny().orElse(null);
    }
    
    /**
     * 将group转换为列
     * @param group
     * @param name
     * @param align
     */
    void transpose(String group, String name, Alignment align) {
      Column col = findColumn(group);
      if (null == col) {
        col = new Column(group, group.length(), align);
        columns.add(col);
      }
      col.width = Math.max(col.width, name.length());
    }
    
    void transpose(String group, String name) {
      transpose(group, name, Alignment.Left);
    }
  }
  
  // 同一个线程中，方法之间也可能存在嵌套的调用，因此需要使用Stack来管理
  private static final ThreadLocal<Stack<Breakdowns>> waterfall = new ThreadLocal<Stack<Breakdowns>>() {
    @Override
    protected Stack<Breakdowns> initialValue() {
      return new Stack<Breakdowns>();
    }
  };
  
  public static void start(String name, String extension, String group) {
    Breakdowns breakdowns = new Breakdowns();
    breakdowns.begin();
    
    Stack<Breakdowns> stack = waterfall.get();
    if (!stack.isEmpty()) { // 一个全新的分解结构在压栈前，被上一层的分解结构作为一个整体维护
      stack.peek().begin(name, extension, group);
    }
    stack.push(breakdowns);
  }
  
  public static Breakdowns stop(String name, String extension) {
    Stack<Breakdowns> stack = waterfall.get();
    if (stack.isEmpty()) {
      return null;
    }
    Breakdowns breakdowns = stack.pop();
    breakdowns.end();
    
    if (!stack.isEmpty()) { // 一个全新的分解结构在压栈前，被上一层的分解结构作为一个整体维护
      stack.peek().end(name, extension);
    }
    
    return breakdowns;
  }
  
  public static void begin(String name, String extension, String group) {
    Stack<Breakdowns> stack = waterfall.get();
    if (!stack.isEmpty()) {
      Breakdowns breakdowns = stack.peek();
      breakdowns.begin(name, extension, group);
    }
  }
  
  public static void end(String name, String extension) {
    Stack<Breakdowns> stack = waterfall.get();
    if (!stack.isEmpty()) {
      Breakdowns breakdowns = stack.peek();
      breakdowns.end(name, extension);
    }
  }
  
  public static void begin(String name, String group) {
    begin(name, null, group);
  }
  
  public static void end(String name) {
    end(name, null);
  }
}
